package model.tokens;

import java.util.ArrayList;
import java.util.List;
import java.util.Queue;

import model.parser.FunctionTable;
import model.parser.ParserException;
import model.parser.UserFunctionDefinition;
import model.semantics.AggregatorNode;
import model.semantics.LabelNode;
import model.semantics.Node;
import model.semantics.VariableNode;
import model.semantics.DefaultNode;

/**
 * Token representing the to command. The reason it's a token is twofold:
 * because it can take parameters optionally, so there's no fixed order, and
 * because it has to modify the FunctionTable during parsing. It extends
 * FunctionToken<T> so that it has the same process() functionality.
 * 
 * @author Sterling Dorminey
 * 
 */
public class ToToken<T> extends Token<T> {
    private FunctionTable<T> functionTable;

    public ToToken(TokenConstructorArguments<T> args) {
        super(args);

        functionTable = args.functionTable;
    }

    public Node<T> makeNewInstance(Queue<Node<T>> nodes) {

        /*
         * The node queue will either be like this: to label [ arg list ] [
         * function body ], or to label [ function body ] <something not an
         * aggregator node.> Use lookahead to determine whether this sucker has
         * args.
         */
        LabelNode<T> label = (LabelNode<T>) nodes.poll();
        AggregatorNode<T> argListOrBody = (AggregatorNode<T>) nodes.poll();
        List<String> argList = new ArrayList<String>();

        // If this is true, then there is a separate function body, so treat the
        // above
        // as an args list.
        if (nodes.peek() instanceof AggregatorNode) {
            AggregatorNode<T> body = (AggregatorNode<T>) nodes.poll();
            // Grab all the args out of the args list.
            for (Node<T> node : argListOrBody.getList()) {
                argList.add(((VariableNode<T>) node).getLabel());
            }
            functionTable.addUserDefinedFunction(label.getLabel(), body,
                    argList);
        } else {
            functionTable.addUserDefinedFunction(label.getLabel(),
                    argListOrBody, argList);
        }

        return new DefaultNode<T>();
    }

    @Override
    public void process(Automaton<T> state) {
        shuntFunctionsToOutput(state.operatorStack, state.output);

        Automaton<T> inner = state.makeBlankAutomaton();

        // Process the label and arg list tokens.
        inner.processTokens(2);
        Node<T> _label = inner.output.poll();
        if (!(_label instanceof LabelNode))
            throw ParserException.BAD_ARGUMENTS;
        LabelNode<T> label = (LabelNode<T>) _label;
        Node<T> _argNode = inner.output.poll();
        if (!(_argNode instanceof AggregatorNode))
            throw ParserException.BAD_ARGUMENTS;
        AggregatorNode<T> argNode = (AggregatorNode<T>) _argNode;

        // Get the strings out of the arg list.
        List<String> argList = new ArrayList<String>();
        for (Node<T> node : argNode.getList()) {
            if (!(node instanceof VariableNode))
                throw ParserException.BAD_ARGUMENTS;
            argList.add(((VariableNode<T>) node).getLabel());
        }

        // Add function definition (used for recursion.)
        functionTable.addUserDefinedFunction(label.getLabel(), null, argList);

        // Now get function body.
        inner.processTokens(1);

        // Now update the function definition to have the new function body.
        Node<T> body = inner.output.poll();
        functionTable.setFunctionBody(label.getLabel(), body);

        // Add a node that returns default value.
        state.output.add(new DefaultNode<T>());
        state.merge(inner);

        shuntFunctionsToOutput(state.operatorStack, state.output);
    }

}
